//简介
// 1. 装饰器本质是⼀种特殊的函数，它可以对：类、属性、⽅法、参数进⾏扩展，同时能让代码更简洁。
// 2. 装饰器⾃ 2015 年在 ECMAScript-6 中被提出到现在，已将近10年。
// 3. 截⽌⽬前，装饰器依然是实验性特性 ，需要开发者⼿动调整配置，来开启装饰器⽀持。
// 4. 装饰器有 5 种：
// 类装饰器
// 属性装饰器
// ⽅法装饰器
// 访问器装饰器
// 参数装饰器

// 备注：虽然 TypeScript5.0 中可以直接使⽤ 类装饰器 ，但为了确保其他装饰器可⽤，现阶段使⽤时，仍建议使⽤
// experimentalDecorators 配置来开启装饰器⽀持，⽽且不排除在来的版本中，官⽅会进⼀步调整装饰器的相关语法！

//类装饰器
//1. 基本语法
//类装饰器是⼀个应⽤在类声明上的函数，可以为类添加额外的功能，或添加额外的逻辑。
//基本语法

//类装饰器
//  Demo函数会在Person类定义时执⾏
//  参数说明：
//  ○ target参数是被装饰的类，即：Person

function Demo(target: Function) {
    // console.log(target.prototype)
    target.prototype.xw = 19
    target.prototype.toString = function () {
        // console.log(this)
        return JSON.stringify(this)
    }
    // 封闭其原型对象，禁⽌随意操作其原型对象
    // Object.seal(target.prototype)
}

//类
@Demo
class Person12 {
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }
}
//简写
// class Person2 {
//     constructor(public name: string, public age: number) {

//     }
// }

const p13 = new Person12('漳卅', 18)
// console.log(JSON.stringify(p12))

//应⽤举例
// 需求：定义⼀个装饰器，实现 Person 实例调⽤ toString 时返回 JSON.stringify 的执⾏结果。
console.log(p13.toString())
// interface Person12 {
//     x: number
// }
//@ts-ignore
Person12.prototype.x = 99
//@ts-ignore
// console.log(p12.x)

//关于返回值
// 类装饰器有返回值：若类装饰器返回⼀个新的类，那这个新类将替换掉被装饰的类。
// 类装饰器⽆返回值：若类装饰器⽆返回值或返回 undefined ，那被装饰的类不会被替换。
function Demo2(target: Function) {
    return class {
        constructor(public name: string, public age: number) {

        }
        // 装饰器有返回值时，该返回值会替换掉被装饰的类
        test() {
            console.log(200)
            console.log(300)
            console.log(400)
        }
    }
}
@Demo2
class Person13 {
    name: string
    age: number
    constructor(name: string, age: number) {
        this.name = name
        this.age = age
    }

    test() {
        console.log(100)
    }
}
// console.log(Person13)
const p14 = new Person13("123", 123)
// p14.test()


//关于构造类型
// 在 TypeScript 中， Function 类型所表示的范围⼗分⼴泛，包括：普通函数、箭头函数、⽅法等等。但并⾮
// Function 类型的函数都可以被 new 关键字实例化，例如箭头函数是不能
// 被实例化的，那么 TypeScript 中概如何声明⼀个构造类型呢？有以下两种⽅式：

//仅声明构造类型
/*
 ○ new 表示：该类型是可以⽤new操作符调⽤。
 ○ ...args 表示：构造器可以接受【任意数量】的参数。
 ○ any[] 表示：构造器可以接受【任意类型】的参数。
 ○ {} 表示：返回类型是对象(⾮null、⾮undefined的对象)。
*/
// 定义Constructor类型，其含义是构造类型
type Constructor = new (...args: any[]) => {};
function test(fn: Constructor) {

}
class Person14 {

}
test(Person14)


//声明构造类型 + 指定静态属性 
type Constructor2 = {
    new(...args: any[]): {}  //约束构造类型 new()
    wife: string   //约束含有静态属性
}

class Person15 {
    static wife: string   //静态属性
    constructor(public age: number) {

    }
}
function test2(fn: Constructor2) {

}
// Person15.wife = "123"
// console.log(Person15.wife)
test2(Person15)


//替换被装饰的类
// 对于⾼级⼀些的装饰器，不仅仅是覆盖⼀个原型上的⽅法，还要有更多功能，例如添加新的⽅法和状态。
// 需求：设计⼀个 LogTime 装饰器，可以给实例添加⼀个属性，⽤于记录实例对象的创建时间，再添加⼀个⽅法⽤于读取创建时间。
type Constructor3 = new (...args: any[]) => {}

//泛型约束
function LogTime<T extends Constructor3>(target: T) {
    return class extends target {
        createTime: Date
        constructor(...args: any[]) {
            super(...args)
            this.createTime = new Date()
        }

        getTime() {
            return `该对象的创建时间为${this.createTime}`
        }
    }
}

interface Person16 {
    getTime(): void
}

@LogTime
class Person16 {
    constructor(public name: string, public age: number) {

    }

    speack() {
        console.log('你好啊')
    }
}
const p16 = new Person16('张三', 18)
//@ts-ignore
console.log(p16.getTime())
